package com.carrot.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 *
 主要思路就是在优先级最高的过滤器里面，
 CacheBodyGlobalFilter这个全局过滤器的目的就是把原有的request请求中的body内容读出来，
 并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装，
 重写getBody方法，并把包装后的请求放到过滤器链中传递下去。

 这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时，
 实际上就是调用的重载后的getBody方法，获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。
 */
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (exchange.getRequest().getHeaders().getContentType() == null) {
            return chain.filter(exchange);
        } else {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        // 释放内存
                        DataBufferUtils.release(dataBuffer);
                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return Flux.defer(()->{
                                    DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                                    DataBufferUtils.retain(buffer);
                                    return Mono.just(buffer);
                                });
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest).build());
                    });
        }
    }


    @Override
    public int getOrder() {
        return -2;
    }
}
